﻿Imports Microsoft.VisualStudio.DebuggerVisualizers
Imports System.Data.Common
Imports System.Data.Objects
Imports System.Data.Objects.DataClasses
Imports System.IO
Imports System.Reflection
Imports System.Text
Imports System.Windows.Forms

Public Class EntityVisualizerObjectSource
    Inherits VisualizerObjectSource

    '''' <summary>
    '''' </summary>
    '''' <param name="target"></param>
    '''' <param name="outgoingData"></param>
    '''' <remarks>Warning: works only with entities that have relationships</remarks>
    Public Overrides Sub GetData(ByVal target As Object, ByVal outgoingData As Stream)
        Dim entity As EntityObject = TryCast(target, EntityObject)
        If entity Is Nothing Then
            GetDataForSelfTrackingEntity(target, outgoingData)
        Else
            GetDataForEntity(entity, outgoingData)
        End If
    End Sub

    '''' <summary>
    '''' </summary>
    '''' <param name="target"></param>
    '''' <param name="outgoingData"></param>
    '''' <remarks>Warning: works only with entities that have relationships</remarks>
    Private Sub GetDataForEntity(ByVal entity As EntityObject, ByVal outgoingData As Stream)
        If (entity.EntityKey Is Nothing) Then
            Throw New Exception("Entity does not have an EntityKey")
        End If

        Dim context As ObjectContext = Utility.GetContextFromEntity(entity)
        Dim ose As ObjectStateEntry = Nothing
        'If object is Detached, then there will be no entry in the ObjectStateManager
        'new entities that are not attached will not even have an entitykey
        If (Not context.ObjectStateManager.TryGetObjectStateEntry(entity.EntityKey, ose)) Then
            Throw New Exception("Object is not currently being change tracked and no ObjectStateEntry exists.")
        End If

        Dim ds As New DataSet()
        ds.Tables.Add(New DataTable())
        ds.Tables(0).Columns.Add("Index").DataType = GetType(Integer)
        ds.Tables(0).Columns.Add("PropertyName").DataType = GetType(String)
        ds.Tables(0).Columns.Add("CurrentValue").DataType = GetType(String)
        ds.Tables(0).Columns.Add("OriginalValue").DataType = GetType(String)
        ds.Tables(0).Columns.Add("Modified").DataType = GetType(Boolean)

        Dim values As CurrentValueRecord = ose.CurrentValues

        'If Object is Added, there will be no Original values and it will throw an exception
        Dim originalValues As DbDataRecord = Nothing
        If ose.State <> EntityState.Added Then
            originalValues = ose.OriginalValues
        End If

        ' get the values
        For i = 0 To values.FieldCount - 1

            Dim row As DataRow = ds.Tables(0).NewRow()
            row.Item("Index") = i + 1

            'metadata provides field names
            Dim sName As String = values.DataRecordInfo.FieldMetadata(i).FieldType.Name

            row.Item("PropertyName") = sName

            Dim isDbDataRecord As Boolean = False
            Dim currVal As Object = values.Item(i)

            'test for complex types
            If TypeOf (currVal) Is DbDataRecord Then
                isDbDataRecord = True
                currVal = DbDataRecordToString(DirectCast(currVal, DbDataRecord))
            End If
            row.Item("CurrentValue") = currVal

            Dim origVal As Object
            If ose.State = EntityState.Added Then
                origVal = "n/a"
            Else
                If Not isDbDataRecord Then
                    origVal = originalValues.Item(i)
                Else
                    origVal = DbDataRecordToString(CType(originalValues.Item(i), DbDataRecord))
                End If
            End If

            row.Item("OriginalValue") = origVal

            Dim changedProp As String = (From prop In ose.GetModifiedProperties _
                                         Where prop = sName).FirstOrDefault
            row.Item("Modified") = (changedProp IsNot Nothing)

            ds.Tables(0).Rows.Add(row)
        Next

        ds.ExtendedProperties.Add("EntityName", entity.ToString)
        ds.ExtendedProperties.Add("EntityState", ose.State.ToString)

        ds.AcceptChanges()

        ds.WriteXml(outgoingData, XmlWriteMode.WriteSchema)
    End Sub

    '''' <summary>
    '''' </summary>
    '''' <param name="entity"></param>
    '''' <param name="outgoingData"></param>
    '''' <remarks>Warning: works only with entities that have relationships</remarks>
    Private Sub GetDataForSelfTrackingEntity(ByVal entity As Object, ByVal outgoingData As Stream)
        Dim dataMemberProps As List(Of PropertyInfo) = Utility.GetPropertyInfos(entity)
        Dim state As String = Utility.GetStateFromEntity(entity)

        Dim ds As New DataSet()
        ds.Tables.Add(New DataTable())
        ds.Tables(0).Columns.Add("Index").DataType = GetType(Integer)
        ds.Tables(0).Columns.Add("PropertyName").DataType = GetType(String)
        ds.Tables(0).Columns.Add("CurrentValue").DataType = GetType(String)
        ds.Tables(0).Columns.Add("OriginalValue").DataType = GetType(String)
        ds.Tables(0).Columns.Add("Modified").DataType = GetType(Boolean)

        ' get the values
        For i = 0 To dataMemberProps.Count - 1

            Dim prop As PropertyInfo = dataMemberProps(i)

            Dim row As DataRow = ds.Tables(0).NewRow()
            row.Item("Index") = i + 1

            row.Item("PropertyName") = prop.Name

            Dim currentValue As String = Utility.NullableObjectToString(prop.GetValue(entity, Nothing))
            row.Item("CurrentValue") = currentValue

            Dim originalValue As String = String.Empty
            If (state = "Added") Then
                originalValue = "n/a"
            Else
                originalValue = Utility.NullableObjectToString(Utility.GetOriginalValue(entity, prop.Name))
                If (originalValue = "Unchanged") Then originalValue = currentValue
            End If
            row.Item("OriginalValue") = originalValue

            row.Item("Modified") = Utility.IsPropertyChanged(entity, prop.Name)

            ds.Tables(0).Rows.Add(row)
        Next

        ds.ExtendedProperties.Add("EntityName", entity.ToString)
        ds.ExtendedProperties.Add("EntityState", state)

        ds.AcceptChanges()

        ds.WriteXml(outgoingData, XmlWriteMode.WriteSchema)
    End Sub

    Private Function DbDataRecordToString(ByVal item As DbDataRecord) As String
        Dim sb = New StringBuilder
        For i = 0 To item.FieldCount - 1
            If item(i) Is DBNull.Value Then
                sb.AppendLine("")
            Else
                sb.AppendLine(item(i).ToString)
            End If
        Next
        Return sb.ToString
    End Function

End Class
